/*
 * Copyright (c) Hisilicon Technologies Co., Ltd. 2019-2019. All rights reserved.
 * Description: host uvc mode interface
 * Author: HiMobileCam Reference Develop Team
 * Create: 2019-09-17
 */

#include "hi_host_uvc.h"
#include <unistd.h>
#include <pthread.h>
#include <poll.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/mman.h>
#include "implementation/usb_init.h"
#include "implementation/global_implementation.h"
#include "video/uvideoio.h"
#include "video/video_if.h"
#include "fs/file.h"

#include "mpi_sys.h"
#include "mpi_vb.h"
#include "mpi_ive.h"
#include "mpi_vpss.h"
#include "hi_appcomm_util.h"

#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif
#endif /* End of #ifdef __cplusplus */

#define USB_VIDEO_DEV                    "/dev/uvideo"
#define MMAP_QUEUE_NR                    3
#define IVE_ALIGN                        16
#define USB_IVE_CALCSTRIDE(width, align) ((width) + ((align) - (width) % (align)) % (align))

/* Host UVC Context */
typedef struct {
    HI_U64 phyAddr;
    HI_U64 virAddr;
    HI_U32 size;
} USB_HostUvcFrmBuf;

/* Host UVC Context */
typedef struct {
    HI_S32 fd;
    HI_BOOL run;
    VB_POOL pool;
    pthread_t taskId;
    HI_USB_HostUvcCfg cfg;
    HI_USB_HostUvcCapability capability;
    USB_HostUvcFrmBuf buffer[MMAP_QUEUE_NR];
} USB_HostUvcCtx;

static USB_HostUvcCtx g_hostUvcCtx = {
    .fd = -1,
    .run = HI_FALSE,
};

static HI_UVC_STREAM_FORMAT_E HostUvcV4L2Fmt2StreamFmt(HI_U32 v4l2fmt)
{
    switch (v4l2fmt) {
        case V4L2_PIX_FMT_YUYV:
            return HI_UVC_STREAM_FORMAT_YUV420;
        case V4L2_PIX_FMT_MJPEG:
            return HI_UVC_STREAM_FORMAT_MJPEG;
        case V4L2_PIX_FMT_H264:
        default:
            return HI_UVC_STREAM_FORMAT_H264;
    }
}

static HI_U32 HostUvcStreamFmt2V4L2Fmt(HI_UVC_STREAM_FORMAT_E fmt)
{
    switch (fmt) {
        case HI_UVC_STREAM_FORMAT_YUV420:
            return V4L2_PIX_FMT_YUYV;
        case HI_UVC_STREAM_FORMAT_MJPEG:
            return V4L2_PIX_FMT_MJPEG;
        case HI_UVC_STREAM_FORMAT_H264:
        default:
            return V4L2_PIX_FMT_H264;
    }
}

static VB_BLK HostUvcCreateIveImage(IVE_IMAGE_S *dstImage, HI_U32 width, HI_U32 height)
{
    dstImage->enType = IVE_IMAGE_TYPE_YUV422SP;
    dstImage->u32Width = width;
    dstImage->u32Height = height;
    dstImage->au32Stride[0] = USB_IVE_CALCSTRIDE(width, IVE_ALIGN);
    HI_U32 size = dstImage->au32Stride[0] * height * 2U;
    VB_BLK vbBlkHdl = HI_MPI_VB_GetBlock(g_hostUvcCtx.pool, size, NULL);
    if (VB_INVALID_HANDLE == vbBlkHdl) {
        MLOGE("HI_MPI_VB_GetBlock fail,u32Size=%d\n", size);
        return VB_INVALID_HANDLE;
    }
    dstImage->au64PhyAddr[0] = HI_MPI_VB_Handle2PhysAddr(vbBlkHdl);
    dstImage->au64PhyAddr[1] = dstImage->au64PhyAddr[0] + dstImage->au32Stride[0] * dstImage->u32Height;
    dstImage->au64PhyAddr[2] = dstImage->au64PhyAddr[1] + dstImage->au32Stride[0] * dstImage->u32Height / 2U;
    dstImage->au64VirAddr[0] = (HI_U64)(HI_UL)HI_MPI_SYS_Mmap(dstImage->au64PhyAddr[0], size);
    dstImage->au64VirAddr[1] = dstImage->au64VirAddr[0] + dstImage->au32Stride[0] * dstImage->u32Height;
    dstImage->au64VirAddr[2] = dstImage->au64VirAddr[1] + dstImage->au32Stride[0] * dstImage->u32Height / 2U;
    dstImage->au32Stride[1] = dstImage->au32Stride[0];
    dstImage->au32Stride[2] = dstImage->au32Stride[0];
    return vbBlkHdl;
}

static HI_S32 HostUvcYuyvToSemiPlanar(HI_U32 width, HI_U32 height,
                                      IVE_SRC_MEM_INFO_S *srcMemInfo, IVE_DST_IMAGE_S *dstImage)
{
    IVE_DMA_CTRL_S dmaCtrl = { IVE_DMA_MODE_INTERVAL_COPY, 0, 2, 1, 1 };
    IVE_HANDLE iveHdl;

    VB_BLK vbBlk = HostUvcCreateIveImage(dstImage, width, height);
    if (vbBlk == VB_INVALID_HANDLE) {
        return HI_FAILURE;
    }

    HI_U32 stride = USB_IVE_CALCSTRIDE(width * 2, IVE_ALIGN);
    /* deal with left part for y pixel */
    IVE_SRC_DATA_S dataSrc = { 0 };
    dataSrc.u64VirAddr = srcMemInfo->u64VirAddr;
    dataSrc.u64PhyAddr = srcMemInfo->u64PhyAddr;
    dataSrc.u32Width = width;
    dataSrc.u32Height = height;
    dataSrc.u32Stride = stride;

    IVE_DST_DATA_S dataDst = { 0 };
    dataDst.u64VirAddr = dstImage->au64VirAddr[0];
    dataDst.u64PhyAddr = dstImage->au64PhyAddr[0];
    dataDst.u32Width = width / 2U;
    dataDst.u32Height = height;
    dataDst.u32Stride = dstImage->au32Stride[0];
    HI_S32 ret = HI_MPI_IVE_DMA(&iveHdl, &dataSrc, &dataDst, &dmaCtrl, HI_FALSE);
    if (ret != HI_SUCCESS) {
        goto err;
    }

    /* deal with right part for y pixel */
    dataSrc.u64VirAddr = srcMemInfo->u64VirAddr + width;
    dataSrc.u64PhyAddr = srcMemInfo->u64PhyAddr + width;
    dataSrc.u32Width = width;
    dataSrc.u32Height = height;
    dataSrc.u32Stride = stride;

    dataDst.u64VirAddr = dstImage->au64VirAddr[0] + width / 2U;
    dataDst.u64PhyAddr = dstImage->au64PhyAddr[0] + width / 2U;
    dataDst.u32Width = width / 2U;
    dataDst.u32Height = height;
    dataDst.u32Stride = dstImage->au32Stride[0];
    ret = HI_MPI_IVE_DMA(&iveHdl, &dataSrc, &dataDst, &dmaCtrl, HI_FALSE);
    if (ret != HI_SUCCESS) {
        goto err;
    }

    /* deal with left part for uv pixel */
    dataSrc.u64VirAddr = srcMemInfo->u64VirAddr + sizeof(HI_U8);
    dataSrc.u64PhyAddr = srcMemInfo->u64PhyAddr + sizeof(HI_U8);
    dataSrc.u32Width = width;
    dataSrc.u32Height = height;
    dataSrc.u32Stride = stride;

    dataDst.u64VirAddr = dstImage->au64VirAddr[1];
    dataDst.u64PhyAddr = dstImage->au64PhyAddr[1];
    dataDst.u32Width = width / 2U;
    dataDst.u32Height = height;
    dataDst.u32Stride = dstImage->au32Stride[1];
    ret = HI_MPI_IVE_DMA(&iveHdl, &dataSrc, &dataDst, &dmaCtrl, HI_FALSE);
    if (ret != HI_SUCCESS) {
        goto err;
    }

    /* deal with right part for uv pixel */
    dataSrc.u64VirAddr = srcMemInfo->u64VirAddr + sizeof(HI_U8) + width;
    dataSrc.u64PhyAddr = srcMemInfo->u64PhyAddr + sizeof(HI_U8) + width;
    dataSrc.u32Width = width;
    dataSrc.u32Height = height;
    dataSrc.u32Stride = stride;

    dataDst.u64VirAddr = dstImage->au64VirAddr[1] + width / 2U;
    dataDst.u64PhyAddr = dstImage->au64PhyAddr[1] + width / 2U;
    dataDst.u32Width = width / 2U;
    dataDst.u32Height = height;
    dataDst.u32Stride = dstImage->au32Stride[1];
    ret = HI_MPI_IVE_DMA(&iveHdl, &dataSrc, &dataDst, &dmaCtrl, HI_FALSE);
    if (ret != HI_SUCCESS) {
        goto err;
    }

    do {
        HI_BOOL finish = HI_FALSE;
        ret = HI_MPI_IVE_Query(iveHdl, &finish, HI_TRUE);
        HI_usleep(10U * 1000U);
    } while (ret == HI_ERR_IVE_QUERY_TIMEOUT);
    if (ret != HI_SUCCESS) {
        goto err;
    }
    return HI_SUCCESS;
err:
    HI_MPI_VB_ReleaseBlock(vbBlk);
    return HI_FAILURE;
}

static HI_VOID HostUvcSendFrameToVpss(HI_HANDLE vpssHdl, const IVE_DST_IMAGE_S *image, HI_U32 seq, HI_U64 pts)
{
    VB_BLK vbBlk = HI_MPI_VB_PhysAddr2Handle(image->au64PhyAddr[0]);
    VIDEO_FRAME_INFO_S stVideoFrame;
    memset(&stVideoFrame, 0, sizeof(VIDEO_FRAME_INFO_S));
    stVideoFrame.u32PoolId = HI_MPI_VB_Handle2PoolId(vbBlk);
    stVideoFrame.enModId = HI_ID_USER;
    stVideoFrame.stVFrame.u32Width = image->u32Width;
    stVideoFrame.stVFrame.u32Height = image->u32Height;
    stVideoFrame.stVFrame.enField = VIDEO_FIELD_FRAME;
    stVideoFrame.stVFrame.enPixelFormat = PIXEL_FORMAT_YUV_SEMIPLANAR_422;
    stVideoFrame.stVFrame.enVideoFormat = VIDEO_FORMAT_LINEAR;
    stVideoFrame.stVFrame.enCompressMode = COMPRESS_MODE_NONE;
    stVideoFrame.stVFrame.enDynamicRange = DYNAMIC_RANGE_SDR8;
    stVideoFrame.stVFrame.enColorGamut = COLOR_GAMUT_BT709;
    stVideoFrame.stVFrame.u32Stride[0] = image->au32Stride[0];
    stVideoFrame.stVFrame.u32Stride[1] = image->au32Stride[1];
    stVideoFrame.stVFrame.u32Stride[2] = image->au32Stride[2];
    stVideoFrame.stVFrame.u64PhyAddr[0] = image->au64PhyAddr[0];
    stVideoFrame.stVFrame.u64PhyAddr[1] = image->au64PhyAddr[1];
    stVideoFrame.stVFrame.u64PhyAddr[2] = image->au64PhyAddr[2];
    stVideoFrame.stVFrame.u64VirAddr[0] = image->au64VirAddr[0];
    stVideoFrame.stVFrame.u64VirAddr[1] = image->au64VirAddr[1];
    stVideoFrame.stVFrame.u64VirAddr[2] = image->au64VirAddr[2];
    stVideoFrame.stVFrame.u32MaxLuminance = 1000U;
    stVideoFrame.stVFrame.u32MinLuminance = 200U;
    stVideoFrame.stVFrame.u32TimeRef = seq * 2U;
    stVideoFrame.stVFrame.u64PTS = pts;

    static JPEG_DCF_S jpegDcf;
    memset(&jpegDcf, 0, sizeof(JPEG_DCF_S));
    stVideoFrame.stVFrame.stSupplement.u64JpegDCFPhyAddr = (HI_U64)(HI_UL)&jpegDcf;
    stVideoFrame.stVFrame.stSupplement.pJpegDCFVirAddr = &jpegDcf;

    HI_S32 ret = HI_MPI_VPSS_SendFrame(vpssHdl, 0, &stVideoFrame, 0);
    if (ret != HI_SUCCESS) {
        MLOGE("HI_MPI_VPSS_SendFrame error ret:%d\n", ret);
    }
    return;
}

/* query device supported operations */
static HI_S32 HostUvcQueryCapability(HI_S32 fd)
{
    struct v4l2_capability caps;
    memset(&caps, 0, sizeof(struct v4l2_capability));
    if (ioctl(fd, VIDIOC_QUERYCAP, &caps) == -1) {
        MLOGE("ioctl VIDIOC_QUERYCAP error:%s\n", strerror(errno));
        return HI_FAILURE;
    }
    MLOGD("Driver       : %s\n", caps.driver);
    MLOGD("Card         : %s\n", caps.card);
    MLOGD("Bus Info     : %s\n", caps.bus_info);
    MLOGD("Version      : %d\n", caps.version);
    if (caps.capabilities & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING)) {
        return HI_SUCCESS;
    } else {
        MLOGE("device supported operations is unexpected:0x%08X\n", caps.capabilities);
        return HI_FAILURE;
    }
}

static HI_S32 HostUvcEnumFrameIntervals(HI_S32 fd, HI_U32 pixfmt, HI_U32 width, HI_U32 height,
                                        HI_USB_HostUvcCapability *capability)
{
    struct v4l2_frmivalenum frameintervals;
    memset(&frameintervals, 0, sizeof(struct v4l2_frmivalenum));
    frameintervals.pixel_format = pixfmt;
    frameintervals.width = width;
    frameintervals.height = height;
    while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frameintervals) == 0) {
        if (frameintervals.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
            if (capability->capCnt >= HI_USB_HOSTUVC_CAP_MAXCNT) {
                MLOGE("Capability set reaches maximum.\n");
                return HI_FAILURE;
            }
            capability->cap[capability->capCnt].pixelFormat = HostUvcV4L2Fmt2StreamFmt(pixfmt);
            capability->cap[capability->capCnt].width = width;
            capability->cap[capability->capCnt].height = height;
            capability->cap[capability->capCnt].fps =
                (HI_FLOAT)frameintervals.discrete.denominator / frameintervals.discrete.numerator;
            MLOGD("Interval: Discrete %fs (%f fps)\n",
                  (HI_FLOAT)frameintervals.discrete.numerator / frameintervals.discrete.denominator,
                  capability->cap[capability->capCnt].fps);
            capability->capCnt++;
        }
        frameintervals.index++;
    }
    if (errno != EINVAL) {
        MLOGE("ioctl VIDIOC_ENUM_FRAMEINTERVALS error:%s\n", strerror(errno));
        return HI_FAILURE;
    }
    return HI_SUCCESS;
}

static HI_S32 HostUvcEnumFrameSize(HI_S32 fd, HI_U32 pixfmt, HI_USB_HostUvcCapability *capability)
{
    struct v4l2_frmsizeenum frameSize;
    memset(&frameSize, 0, sizeof(struct v4l2_frmsizeenum));
    frameSize.pixel_format = pixfmt;
    while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frameSize) == 0) {
        if (frameSize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
            MLOGD("Size: Discrete %ux%u\n", frameSize.discrete.width, frameSize.discrete.height);
            HI_S32 ret = HostUvcEnumFrameIntervals(fd, pixfmt, frameSize.discrete.width, frameSize.discrete.height,
                                                   capability);
            if (ret != HI_SUCCESS) {
                return HI_FAILURE;
            }
        }
        frameSize.index++;
    }
    if (errno != EINVAL) {
        MLOGE("ioctl VIDIOC_ENUM_FRAMESIZES error:%s\n", strerror(errno));
        return HI_FAILURE;
    }
    return HI_SUCCESS;
}

static HI_S32 HostUvcEnumFmt(HI_S32 fd, HI_USB_HostUvcCapability *capability)
{
    struct v4l2_fmtdesc fmtDesc;
    memset(&fmtDesc, 0, sizeof(struct v4l2_fmtdesc));
    fmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    capability->capCnt = 0;
    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtDesc) == 0) {
        MLOGD("Pixel Format:%s (0x%x)\n", fmtDesc.description, fmtDesc.pixelformat);
        HI_S32 ret = HostUvcEnumFrameSize(fd, fmtDesc.pixelformat, capability);
        if (ret != HI_SUCCESS) {
            return HI_FAILURE;
        }
        fmtDesc.index++;
    }
    if (errno != EINVAL) {
        MLOGE("ioctl VIDIOC_ENUM_FMT error:%s\n", strerror(errno));
        return HI_FAILURE;
    }
    return HI_SUCCESS;
}

static HI_S32 HostUvcSetFormat(HI_S32 fd, HI_UVC_STREAM_FORMAT_E pixelFormat, HI_U32 width, HI_U32 height)
{
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(struct v4l2_format));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = width;
    fmt.fmt.pix.height = height;
    fmt.fmt.pix.pixelformat = HostUvcStreamFmt2V4L2Fmt(pixelFormat);
    fmt.fmt.pix.field = V4L2_FIELD_ANY;
    if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
        MLOGE("ioctl VIDIOC_S_FMT error:%s\n", strerror(errno));
        return HI_FAILURE;
    }
    return HI_SUCCESS;
}

static HI_S32 HostUvcDequeueBuffer(HI_S32 fd, HI_S32 *idx, HI_S32 timeoutMs)
{
    HI_S32 r;
    struct v4l2_buffer buffer;
    struct pollfd pfds[1];
    if (timeoutMs > 0) {
        pfds[0].fd = fd;
        pfds[0].events = POLLIN;
        r = poll(pfds, 1, timeoutMs);
        if (r == -1) {
            MLOGE("poll fail\n");
            goto end;
        }
        if (r == 0) {
            MLOGE("poll timeout\n");
            goto end;
        }
    }

    /* dequeue buffers */
    memset(&buffer, 0, sizeof(struct v4l2_buffer));
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buffer.memory = V4L2_MEMORY_USERPTR;
    r = ioctl(fd, VIDIOC_DQBUF, &buffer);
    if (r == -1) {
        MLOGE("VIDIOC_DQBUF fail\n");
        goto end;
    }
    *idx = buffer.index;
    return (buffer.bytesused);
end:
    *idx = -1;
    return HI_FAILURE;
}

static HI_S32 HostUvcQueueBuffer(USB_HostUvcCtx *hostUvcCtx, HI_S32 idx)
{
    /* queue the buffers */
    struct v4l2_buffer buffer;
    memset(&buffer, 0, sizeof(struct v4l2_buffer));
    buffer.index = idx;
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buffer.memory = V4L2_MEMORY_USERPTR;
    buffer.m.userptr = (unsigned long)hostUvcCtx->buffer[idx].virAddr;
    buffer.length = hostUvcCtx->buffer[idx].size;
    if (ioctl(hostUvcCtx->fd, VIDIOC_QBUF, &buffer) == -1) {
        MLOGE("VIDIOC_QBUF %d fail\n", idx);
        return HI_FAILURE;
    }
    return HI_SUCCESS;
}

static HI_S32 HostUvcRequestBuffer(USB_HostUvcCtx *hostUvcCtx, HI_U32 bufferCnt)
{
    /* request buffers */
    struct v4l2_requestbuffers reqbufs;
    memset(&reqbufs, 0, sizeof(struct v4l2_requestbuffers));
    reqbufs.count = bufferCnt;
    reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbufs.memory = V4L2_MEMORY_USERPTR;
    if (ioctl(hostUvcCtx->fd, VIDIOC_REQBUFS, &reqbufs) == -1) {
        MLOGE("VIDIOC_REQBUFS failed.\n");
        return HI_FAILURE;
    }

    for (HI_S32 i = 0; i < bufferCnt; i++) {
        struct v4l2_buffer buffer;
        memset(&buffer, 0, sizeof(struct v4l2_buffer));
        memset(&hostUvcCtx->buffer[i], 0, sizeof(USB_HostUvcFrmBuf));
        buffer.index = i;
        buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buffer.memory = V4L2_MEMORY_USERPTR;
        if (ioctl(hostUvcCtx->fd, VIDIOC_QUERYBUF, &buffer) == -1) {
            MLOGE("VIDIOC_QUERYBUF fail\n");
            return HI_FAILURE;
        }
        /* mmap the buffers */
        hostUvcCtx->buffer[i].virAddr =
           (HI_U64)(HI_UL)videommap(fs_getfilep(hostUvcCtx->fd), buffer.m.offset, PROT_READ);
        if (hostUvcCtx->buffer[i].virAddr == (HI_U64)(HI_UL)MAP_FAILED) {
            /* allocate frame buffer */
            HI_S32 ret = HI_MPI_SYS_MmzAlloc_Cached(&hostUvcCtx->buffer[i].phyAddr,
                (HI_VOID **)&hostUvcCtx->buffer[i].virAddr, "HostUvcFrameBuf", "anonymous", buffer.length);
            if (ret != HI_SUCCESS) {
                MLOGE("alloc frame buf failed.size=%d\n", buffer.length);
                return HI_FAILURE;
            }
        } else {
            hostUvcCtx->buffer[i].phyAddr = hostUvcCtx->buffer[i].virAddr;
        }
        hostUvcCtx->buffer[i].size = buffer.length;
        /* queue the buffers */
        if (HostUvcQueueBuffer(hostUvcCtx, i) != HI_SUCCESS) {
            MLOGE("VIDIOC_QBUF fail\n");
            return HI_FAILURE;
        }
    }
    return HI_SUCCESS;
}

static HI_S32 HostUvcProcess(USB_HostUvcCtx *ctx, HI_U32 seq, HI_S32 timeoutMs)
{
    HI_S32 ret = HI_SUCCESS;
    HI_S32 idx = 0;
    /* get frame */
    HI_S32 frameLen = HostUvcDequeueBuffer(ctx->fd, &idx, 0);
    if (frameLen <= 0 || idx < 0) {
        return HI_FAILURE;
    }
    HI_U64 pts;
    HI_MPI_SYS_GetCurPTS(&pts);
    HI_MPI_SYS_MmzFlushCache(ctx->buffer[idx].virAddr, (HI_VOID *)(HI_UL)ctx->buffer[idx].virAddr,
                             ctx->buffer[idx].size);

    IVE_DST_IMAGE_S dstImage;
    if (ctx->cfg.pixelFormat == HI_UVC_STREAM_FORMAT_YUV420) {
        IVE_SRC_MEM_INFO_S srcMemInfo;
        srcMemInfo.u64PhyAddr = ctx->buffer[idx].virAddr;
        srcMemInfo.u64VirAddr = ctx->buffer[idx].virAddr;
        srcMemInfo.u32Size = frameLen;
        memset(&dstImage, 0, sizeof(IVE_DST_IMAGE_S));
        /* yuyv2sp422 */
        ret = HostUvcYuyvToSemiPlanar(ctx->cfg.width, ctx->cfg.height, &srcMemInfo, &dstImage);
    }
    (HI_VOID)HostUvcQueueBuffer(ctx, idx);

    if (ret != HI_SUCCESS) {
        MLOGE("HostUvcYuyvToSemiPlanar error ret:%d\n", ret);
        return ret;
    }

    /* frame post process */
    switch (ctx->cfg.destination) {
        case HI_USB_HOSTUVC_DST_VPSS:
            /* send frame to vpss */
            HostUvcSendFrameToVpss(ctx->cfg.vpssHdl, &dstImage, seq, pts);
            break;
        case HI_USB_HOSTUVC_DST_USER:
            if (g_hostUvcCtx.cfg.frameDataProc != NULL) {
                HI_USB_HostUvcFrameInfo frame;
                frame.width = ctx->cfg.width;
                frame.height = ctx->cfg.height;
                frame.seq = seq;
                frame.pts = pts;
                frame.pixelFormat = ctx->cfg.pixelFormat;
                if (frame.pixelFormat == HI_UVC_STREAM_FORMAT_YUV420) {
                    for (HI_U32 i = 0; i < 3; i++) {
                        frame.yuv.phyAddr[i] = dstImage.au64PhyAddr[i];
                        frame.yuv.virAddr[i] = dstImage.au64VirAddr[i];
                        frame.yuv.stride[i] = dstImage.au32Stride[i];
                    }
                } else {
                    frame.mjpeg.data = (HI_U8 *)(HI_UL)ctx->buffer[idx].virAddr;
                    frame.mjpeg.dataLen = frameLen;
                }
                ctx->cfg.frameDataProc(&frame);
            }
            break;
        default:
            MLOGW("destination %d is not support.\n", ctx->cfg.destination);
            break;
    }
    HI_MPI_VB_ReleaseBlock(HI_MPI_VB_PhysAddr2Handle(dstImage.au64PhyAddr[0]));
    return HI_SUCCESS;
}

static HI_VOID *HostUvcTask(HI_VOID *arg)
{
    prctl(PR_SET_NAME, "Host_UVC_Task", 0, 0, 0);
    USB_HostUvcCtx *hostUvcCtx = (USB_HostUvcCtx *)arg;

    /* get frame interval */
    struct v4l2_streamparm parm;
    memset(&parm, 0, sizeof(struct v4l2_streamparm));
    parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(hostUvcCtx->fd, VIDIOC_G_PARM, &parm);
    HI_U32 frameIntervalMs =
        parm.parm.capture.timeperframe.numerator / (parm.parm.capture.timeperframe.denominator / 1000U);
    MLOGD("frameIntervalMs=%d \n", frameIntervalMs);

    if (hostUvcCtx->cfg.pixelFormat == HI_UVC_STREAM_FORMAT_YUV420) {
        VB_POOL_CONFIG_S vbPoolCfg;
        memset(&vbPoolCfg, 0, sizeof(VB_POOL_CONFIG_S));
        vbPoolCfg.u64BlkSize = USB_IVE_CALCSTRIDE(hostUvcCtx->cfg.width, IVE_ALIGN) * hostUvcCtx->cfg.height * 2U;
        if (hostUvcCtx->cfg.destination == HI_USB_HOSTUVC_DST_VPSS) {
            vbPoolCfg.u32BlkCnt = 2U;
        } else {
            vbPoolCfg.u32BlkCnt = 1;
        }
        vbPoolCfg.enRemapMode = VB_REMAP_MODE_NONE;
        g_hostUvcCtx.pool = HI_MPI_VB_CreatePool(&vbPoolCfg);
        if (g_hostUvcCtx.pool == VB_INVALID_POOLID) {
            MLOGE("HI_MPI_VB_CreatePool failed. blksize=%lld\n", vbPoolCfg.u64BlkSize);
            goto exit;
        }
    }

    if (HostUvcRequestBuffer(hostUvcCtx, MMAP_QUEUE_NR) != HI_SUCCESS) {
        goto exit;
    }

    /* turn on stream */
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(hostUvcCtx->fd, VIDIOC_STREAMON, &type) == -1) {
        MLOGE("VIDIOC_STREAMON fail\n");
        goto exit;
    }

    HI_U32 seq = 0;
    while (hostUvcCtx->run) {
        if (hostUvcCtx->cfg.frameCnt >= 0 && seq >= hostUvcCtx->cfg.frameCnt) {
            /* reach max frame count */
            break;
        }
        if (USB_HostUvcIsConnected() == HI_FALSE) {
            MLOGE("host uvc is disconnected.\n");
            break;
        }
        if (HostUvcProcess(hostUvcCtx, seq, (frameIntervalMs * 2U)) == HI_SUCCESS) {
            seq++;
        } else {
            HI_usleep(frameIntervalMs * 1000U);
        }
    }

    /* turn off stream */
    if (ioctl(hostUvcCtx->fd, VIDIOC_STREAMOFF, &type) == -1) {
        MLOGE("VIDIOC_STREAMOFF fail\n");
    }

exit:

    for (HI_S32 i = 0; i < MMAP_QUEUE_NR; i++) {
        if (hostUvcCtx->buffer[i].size > 0) {
            /* munmap the buffers */
            munmap ((void *)(HI_UL)hostUvcCtx->buffer[i].virAddr, hostUvcCtx->buffer[i].size);
            /* free frame buffer */
            if (hostUvcCtx->buffer[i].virAddr != 0) {
                HI_MPI_SYS_MmzFree (hostUvcCtx->buffer[i].phyAddr, (HI_VOID *)(HI_UL)hostUvcCtx->buffer[i].virAddr);
                hostUvcCtx->buffer[i].virAddr = 0;
                hostUvcCtx->buffer[i].phyAddr = 0;
                hostUvcCtx->buffer[i].size = 0;
            }
        }
    }

    if (hostUvcCtx->cfg.pixelFormat == HI_UVC_STREAM_FORMAT_YUV420) {
        HI_MPI_VB_DestroyPool(g_hostUvcCtx.pool);
    }

    return NULL;
}

/* ----------------------------------------------------------------- */
HI_S32 USB_HostUvcInit(HI_VOID)
{
    return usb_init(HOST, 0);
}

HI_VOID USB_HostUvcDeinit(HI_VOID)
{
    USB_HostUvcCloseDevice();
    usb_deinit();
}

HI_BOOL USB_HostUvcIsConnected(HI_VOID)
{
    HI_BOOL isConnected = access(USB_VIDEO_DEV, F_OK) ? HI_FALSE : HI_TRUE;
    return isConnected;
}

HI_S32 USB_HostUvcOpenDevice(HI_VOID)
{
    if (g_hostUvcCtx.fd == -1) {
        HI_S32 fd = open(USB_VIDEO_DEV, O_RDWR, 0);
        if (fd == -1) {
            MLOGE("open %s failed.\n", USB_VIDEO_DEV);
            return HI_FAILURE;
        }
        if (HostUvcQueryCapability(fd) != HI_SUCCESS) {
            close(fd);
            return HI_FAILURE;
        }
        if (HostUvcEnumFmt(fd, &g_hostUvcCtx.capability) != HI_SUCCESS) {
            close(fd);
            return HI_FAILURE;
        }
        MLOGD("open %s success, fd = %d\n", USB_VIDEO_DEV, fd);
        g_hostUvcCtx.fd = fd;
    } else {
        MLOGD("Host Uvc device already opened\n");
    }
    return HI_SUCCESS;
}

HI_VOID USB_HostUvcCloseDevice(HI_VOID)
{
    if (g_hostUvcCtx.fd == -1) {
        MLOGD("Host Uvc device not open\n");
        return;
    }
    USB_HostUvcStop();
    close(g_hostUvcCtx.fd);
    g_hostUvcCtx.fd = -1;
}

HI_S32 USB_HostUvcGetCapability(HI_USB_HostUvcCapability *capability)
{
    if (g_hostUvcCtx.fd == -1) {
        MLOGE("Host Uvc device not open\n");
        return HI_FAILURE;
    }
    memcpy(capability, &g_hostUvcCtx.capability, sizeof(HI_USB_HostUvcCapability));
    return HI_SUCCESS;
}

HI_S32 USB_HostUvcStart(const HI_USB_HostUvcCfg *cfg)
{
    if (g_hostUvcCtx.fd == -1) {
        MLOGE("Host Uvc device not open\n");
        return HI_FAILURE;
    }

    if (g_hostUvcCtx.run == HI_TRUE) {
        MLOGD("HostUvc already started\n");
        return HI_SUCCESS;
    }

    /* set format */
    if (HostUvcSetFormat(g_hostUvcCtx.fd, cfg->pixelFormat, cfg->width, cfg->height) != HI_SUCCESS) {
        return HI_FAILURE;
    }

    /* create host uvc task thread */
    g_hostUvcCtx.run = HI_TRUE;
    g_hostUvcCtx.cfg = *cfg;
    if (pthread_create(&g_hostUvcCtx.taskId, NULL, HostUvcTask, &g_hostUvcCtx)) {
        MLOGE("host uvc task create failed\n");
        g_hostUvcCtx.run = HI_FALSE;
        return HI_FAILURE;
    }
    MLOGD("host uvc task create successful\n");
    return HI_SUCCESS;
}

HI_VOID USB_HostUvcStop(HI_VOID)
{
    if (g_hostUvcCtx.run == HI_FALSE) {
        MLOGD("host uvc task not run\n");
        return;
    }

    /* dsestroy host uvc task */
    g_hostUvcCtx.run = HI_FALSE;
    pthread_join(g_hostUvcCtx.taskId, NULL);
}

#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* End of #ifdef __cplusplus */
